import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;

/*
 * Class Lexer
 * 
 * Update the method nextToken() such to the provided
 * specifications of the Decaf Programming Language.
 * 
 * You are not allowed to use any built it in tokenizer
 * in Java. You are only allowed to scan the input file
 * one character at a time.
 */

public class Lexer {

	private BufferedReader reader; // Reader
	private char curr; // The current character being scanned

	private static final char EOF = (char) (-1);
	
	private int eofCheck = 0; // Bool for EOF
	
	private int decimalCheck = 0; // Bool for trailing zeros

	public int lineNumber = 1; // Current line number
	
	public String lineString = ""; // Current line string
	
	public boolean print = false; // Bool for printing warnings

	public int charNumber = 0; // Current char number

	public ArrayList<String> lines = new ArrayList<String>();

	// End of file character

	public Lexer(String file) {
		try {
			// Make an initial pass to save each line in the arraylist
			BufferedReader linesReader = new BufferedReader(new FileReader(file));
			lines.add("anything");
			String thisLine;
			int i = 1;
			while ((thisLine = linesReader.readLine()) != null) {
				thisLine = thisLine.trim();
				lines.add(i, thisLine);
				i ++;
			}

			reader = new BufferedReader(new FileReader(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException f) {
			f.printStackTrace();
		}

		// Read the first character
		curr = read();
	}

	private char read() {
		try {
			charNumber ++;
			char c = (char) (reader.read());
			lineString += c;
			if (c == '\n') {
				if(print){
					System.out.println(lineString);
					print = false;
				}
				lineNumber ++;
				charNumber = 1;
				lineString = "";
				
			}
			return c;
		} catch (IOException e) {
			e.printStackTrace();
			return EOF;
		}
	}

	// Checks if a character is a digit
	private boolean isNumeric(char c) {
		if (c >= '0' && c <= '9')
			return true;

		return false;
	}
	
	// Checks if a character is a letter
	private boolean isLetter(char c) {
		if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
			return true;
			
		return false;
	}

	public Token nextToken() {

		int state = 1; // Initial state
		int numBuffer = 0; // A buffer for number literals
 		String decimalBuffer = ""; // A buffer for decimal literals
		String stringBuffer = ""; // A buffer for strings
		int commentsType = 0; // 0 for singleline, 1 for multiline
		String tempString = ""; // For error msg
		int idHasNum = 0; // Bool for ID
		
		while (true) {
			// if (curr == EOF) {
			// 	try {
			// 		reader.close();					
			// 	} catch (IOException e) {
			// 		e.printStackTrace();
			// 	}
			// 	return null;
			// }
			if (eofCheck == 1) {
				try {
					reader.close();
					return new Token(Token.EOF, "EOF");
				} catch (IOException e) {
					e.printStackTrace();
				}
				return null;
			}
			
			if (curr == EOF) {
				// return whatever u are holding
				eofCheck = 1;
				curr = '\n';
			}
			switch (state) {
			// Controller
			case 1:
				switch (curr) {
					// Whitespace chars - Start
				case ' ': 
					
				case '\n':
					curr = read();
					continue;
					
				case '\b':
					curr = read();
					continue;
				
				case '\f':
					curr = read();
					continue;
					
				case '\r':
					curr = read();
					continue;
					
				case '\t':
					charNumber --;
					curr = read();
					continue;
					// Whitespace chars - End
				
				case '_':
					curr = read();
					state = 7;
					stringBuffer = "" + '_';
					numBuffer = 0;
					continue;
					
				case '"':
					curr = read();
					state = 6;
					stringBuffer = "" + '"';
					continue;
					
				case '.':
					curr=read();
					System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
					print = true;
					break;
					
				case ';':
					curr = read();
					return new Token(Token.SM, ";");
					
				case ',':
					curr = read();
					return new Token(Token.FA, ",");
					
				case '(':
					curr = read();
					return new Token(Token.LP, "(");
					
				case ')':
					curr = read();
					return new Token(Token.RP, ")");
					
				case '{':
					curr = read();
					return new Token(Token.LB, "{");
					
				case '}':
					curr = read();
					return new Token(Token.RB, "}");
		
				case '=':
					curr = read();
					if (curr == '=') {
						curr = read();
						return new Token(Token.EQ, "==");
					} else 
						return new Token(Token.AO, "=");						

				case '+':
					curr = read();
					return new Token(Token.PO, "+");

				case '-':
					curr = read();
					return new Token(Token.MO, "-");

				case '*':
					curr = read();
					return new Token(Token.TO, "*");

				case '/':
					curr = read();
					if (curr == '/') {
						state = 4;
						
						curr = read();
						continue;
					} else if (curr == '*') {
						state = 5;
						
						curr = read();
						continue;
					} else {
						return new Token(Token.DO, "/");
					}
					
				case '%':
					curr = read();
					return new Token(Token.MD, "%");

				case '!':
					tempString = curr + "";
					curr = read();
					tempString += curr + "";
					if (curr == '=') {
						curr = read();
						return new Token(Token.NE, "!=");
					} else if (curr == EOF) {
						System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
						print = true;
						break;
					} else {
						curr = read();
						System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
						print = true;
						break;
					}
						
				case '|':
					tempString = curr + "";
					curr = read();
					tempString += curr + "";
					if (curr == '|') {
						curr = read();
						return new Token(Token.LO, "||");
					} else if (curr == EOF) {
						System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
						print = true;
						break;
					} else {
						System.out.print("\nWarning : At line "+ lineNumber + " char " + charNumber + ": \n| converted to || \nIn: ");
						print = true;
						curr = read();
						return new Token(Token.LO, "||");
					}
						
				case '&':
					tempString = curr + "";
					curr = read();
					tempString += curr + "";
					if (curr == '&') {
						curr = read();
						return new Token(Token.LA, "&&");
					} else if (curr == EOF) {
						System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
						print = true;
						break;
					} else {
						System.out.print("\nWarning : At line "+ lineNumber + " char " + charNumber + ": \n& converted to && \nIn: ");
						print = true;
						curr = read();
						return new Token(Token.LA, "&&");
					}
				default:
					state = 2; // Check for other possibilites
					continue;
				}

				// Integer - Start
			case 2:
				if (isNumeric(curr)) {
					numBuffer = 0; // Reset the buffer.
					numBuffer += (curr - '0');

					state = 3;
					curr = read();
				} else if (isLetter(curr)){
					state = 7;
					stringBuffer = "" + curr;
					curr = read();
				} else {
					tempString = curr + "";
					curr = read();
					System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
					print = true;
					break;
					// return null;
				}
				continue;

				// Integer - Body
			case 3:
				if (isNumeric(curr) || curr == '.') {
					if (curr == '.') {
						stringBuffer = "" + numBuffer + ".";
						numBuffer = 0;
						decimalBuffer = "";
						decimalCheck = 1;
						curr = read();
					}
					if (isNumeric(curr) && decimalCheck == 0) {
						numBuffer *= 10;
						numBuffer += (curr - '0');

						curr = read();						
					} else if (isNumeric(curr) && decimalCheck == 1) {
						decimalBuffer += curr + "";
						
						curr = read();
					} else {
						// curr = read();
						System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
						print = true;
						new Token(Token.NM, numBuffer + "" );
					}
				} else {
					// curr = read();
					eofCheck = 0;
					String test;
					if (decimalCheck == 1) {
						test = "" + decimalBuffer;																	
					} else {
						test = "" + numBuffer;
					}

					// //System.out.println("Before" + test);
					if (decimalCheck == 1 && test.length() > 1) {
						while(test.charAt(test.length()-1) =='0') {
		          test = test.substring(0, test.length()-1);
	          }						
						decimalCheck = 0;
					}

					return new Token(Token.NM, stringBuffer + "" + test);
				}
				continue;
				
				// Comments - Single line
			case 4:	
				if (curr == '\n') {
					// new line detected, close comment
					state = 1;
					curr = read();
					continue;
				}
				if (curr == '/') {
					curr = read();
					if (curr == '/') {
						// stop
						state = 1;
						curr = read();
					} else {
						curr = read();
					}
				} else {
					curr = read();
				}

				continue;

				// Comments - Multi-line
			case 5:
				if (curr == '*') {
					curr = read();
					if (curr == '/') {
						// stop
						state = 1;
						curr = read();
					} else {
						curr = read();
					}
				} else {
					curr = read();
				}
				continue;
				
			case 6:
				if (curr != '\n' && curr != '"' && curr != '\r' && curr != ';') {
					stringBuffer += curr;
					curr = read();
				} else if (curr == '"') {
					curr = read(); // NOTE: Not sure
					return new Token(Token.ST, stringBuffer + '"');
				} else if (curr == '\n' || curr == '\r') {
					curr = read(); // NOTE: Not sure
					System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
					print = true;
					break;
				} else if (curr == ';'){
					System.out.println("\nError Corrected at line " + lineNumber +"\n" + lineString.substring(0, lineString.length()-1) + "\nConverted to :" + lineString.substring(0, lineString.length()-1) + "\"");
					return new Token(Token.ST, stringBuffer + '"');
				}
				continue;
				
				// Identifiers
			case 7:
				if (isLetter(curr)) {
					stringBuffer += curr;
					
					curr = read();
				} else if (isNumeric(curr)) {
					idHasNum = 1;
					numBuffer *= 10;
					numBuffer += (curr - '0');
					
					curr = read();
				} else if (curr == '_') {
					stringBuffer += "" + '_';
					
					curr = read();
				} else {
					// terminate the ID
					// if it's empty, return error, else return token
					if (stringBuffer.length() == 1 && stringBuffer.charAt(0) == '_') {
						// return new Token(-1, "Invalid ID");
						System.out.println( "Warning : Invalid Input at line "+ lineNumber + " char " + charNumber+" and will be ignored " );
						print = true;
						break;
					} else if (stringBuffer.equals("class")){	
						return new Token(Token.CLASS, "class");
					} else if (stringBuffer.equals("else")) {
						return new Token(Token.ELSE, "else");
					} else if (stringBuffer.equals("if")) {
						return new Token(Token.IF, "if");
					} else if (stringBuffer.equals("int")) {
						return new Token(Token.INT, "int");
					} else if (stringBuffer.equals("float")) {
						return new Token(Token.FLOAT, "float");
					} else if (stringBuffer.equals("boolean")) {
						return new Token(Token.BOOLEAN, "boolean");
					} else if (stringBuffer.equals("String")) {
						return new Token(Token.STRING, "String");
					} else if (stringBuffer.equals("return")) {
						return new Token(Token.RETURN, "return");
					} else if (stringBuffer.equals("static")) {
						return new Token(Token.STATIC, "static");
					} else if (stringBuffer.equals("while")) {
						return new Token(Token.WHILE, "while");
					} else if (stringBuffer.equals("true")) {
						return new Token(Token.BL, "true");
					} else if (stringBuffer.equals("false")) {
						return new Token(Token.BL, "false");
					} else if (idHasNum == 1){						
						return new Token(Token.ID, stringBuffer + numBuffer);
					} else {
						return new Token(Token.ID, stringBuffer);
					}
				}
				continue;
			}
		}
	}
}